//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.baomidou.mybatisplus.core.metadata;
import com.baomidou.mybatisplus.annotation.*;
import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.config.GlobalConfig.DbConfig;
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import com.baomidou.mybatisplus.core.toolkit.*;
import com.mdp.core.dao.TableUtil;
import org.apache.ibatis.builder.MapperBuilderAssistant;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.ResultMap.Builder;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.Reflector;
import org.apache.ibatis.reflection.ReflectorFactory;
import org.apache.ibatis.session.Configuration;

import java.lang.reflect.Field;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

public class TableInfoHelper {
    private static final Log logger = LogFactory.getLog(TableInfoHelper.class);
    private static final Map<Class<?>, TableInfo> TABLE_INFO_CACHE = new ConcurrentHashMap();
    private static final Map<String, TableInfo> TABLE_NAME_INFO_CACHE = new ConcurrentHashMap();
    private static final String DEFAULT_ID_NAME = "id";

    public TableInfoHelper() {
    }

    public static TableInfo getTableInfo(Class<?> clazz) {
        if (clazz != null && !ReflectionKit.isPrimitiveOrWrapper(clazz) && clazz != String.class && !clazz.isInterface()) {
            Class<?> targetClass = ClassUtils.getUserClass(clazz);
            TableInfo tableInfo = (TableInfo)TABLE_INFO_CACHE.get(targetClass);
            if (null != tableInfo) {
                return tableInfo;
            } else {
                for(Class currentClass = clazz; null == tableInfo && Object.class != currentClass; tableInfo = (TableInfo)TABLE_INFO_CACHE.get(ClassUtils.getUserClass(currentClass))) {
                    currentClass = currentClass.getSuperclass();
                }

                if (tableInfo != null) {
                    TABLE_INFO_CACHE.put(targetClass, tableInfo);
                }

                return tableInfo;
            }
        } else {
            return null;
        }
    }

    public static TableInfo getTableInfo(String tableName) {
        return StringUtils.isBlank(tableName) ? null : (TableInfo)TABLE_NAME_INFO_CACHE.get(tableName);
    }

    public static List<TableInfo> getTableInfos() {
        return Collections.unmodifiableList(new ArrayList(TABLE_INFO_CACHE.values()));
    }

    public static synchronized TableInfo initTableInfo(MapperBuilderAssistant builderAssistant, Class<?> clazz) {
        TableInfo targetTableInfo = (TableInfo)TABLE_INFO_CACHE.get(clazz);
        Configuration configuration = builderAssistant.getConfiguration();
        if (targetTableInfo != null) {
            Configuration oldConfiguration = targetTableInfo.getConfiguration();
            if (!oldConfiguration.equals(configuration)) {
                targetTableInfo = initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);
            }

            return targetTableInfo;
        } else {
            return initTableInfo(configuration, builderAssistant.getCurrentNamespace(), clazz);
        }
    }

    private static synchronized TableInfo initTableInfo(Configuration configuration, String currentNamespace, Class<?> clazz) {
        TableInfo tableInfo = new TableInfo(configuration,clazz);
        tableInfo.setCurrentNamespace(currentNamespace);
        GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
        String[] excludeProperty = initTableName(clazz, globalConfig, tableInfo);
        List<String> excludePropertyList = excludeProperty != null && excludeProperty.length > 0 ? Arrays.asList(excludeProperty) : Collections.emptyList();
        initTableFields(clazz, globalConfig, tableInfo, excludePropertyList);
        tableInfo.initResultMapIfNeed();
        TABLE_INFO_CACHE.put(clazz, tableInfo);
        TABLE_NAME_INFO_CACHE.put(tableInfo.getTableName(), tableInfo);
        LambdaUtils.installCache(tableInfo);
        return tableInfo;
    }

    private static String[] initTableName(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo) {
        DbConfig dbConfig = globalConfig.getDbConfig();
        TableName table = (TableName)clazz.getAnnotation(TableName.class);
        String tableName = clazz.getSimpleName();
        String tablePrefix = dbConfig.getTablePrefix();
        String schema = dbConfig.getSchema();
        boolean tablePrefixEffect = true;
        String[] excludeProperty = null;
        if (table != null) {
            if (StringUtils.isNotBlank(table.value())) {
                tableName = table.value();
                if (StringUtils.isNotBlank(tablePrefix) && !table.keepGlobalPrefix()) {
                    tablePrefixEffect = false;
                }
            } else {
                tableName = initTableNameWithDbConfig(tableName, dbConfig);
            }

            if (StringUtils.isNotBlank(table.schema())) {
                schema = table.schema();
            }

            if (StringUtils.isNotBlank(table.resultMap())) {
                tableInfo.setResultMap(table.resultMap());
            }

            tableInfo.setAutoInitResultMap(table.autoResultMap());
            excludeProperty = table.excludeProperty();
        } else {
            tableName = initTableNameWithDbConfig(tableName, dbConfig);
        }

        String targetTableName = tableName;
        if (StringUtils.isNotBlank(tablePrefix) && tablePrefixEffect) {
            targetTableName = tablePrefix + tableName;
        }

        if (StringUtils.isNotBlank(schema)) {
            targetTableName = schema + "." + targetTableName;
        }

        tableInfo.setTableName(targetTableName);
        if (CollectionUtils.isNotEmpty(dbConfig.getKeyGenerators())) {
            tableInfo.setKeySequence((KeySequence)clazz.getAnnotation(KeySequence.class));
        }

        return excludeProperty;
    }

    private static String initTableNameWithDbConfig(String className, DbConfig dbConfig) {
        String tableName = className;
        if (dbConfig.isTableUnderline()) {
            tableName = StringUtils.camelToUnderline(className);
        }

        if (dbConfig.isCapitalMode()) {
            tableName = tableName.toUpperCase();
        } else {
            tableName = StringUtils.firstToLowerCase(tableName);
        }

        return tableName;
    }

    private static void initTableFields(Class<?> clazz, GlobalConfig globalConfig, TableInfo tableInfo, List<String> excludeProperty) {
        DbConfig dbConfig = globalConfig.getDbConfig();
        ReflectorFactory reflectorFactory = tableInfo.getConfiguration().getReflectorFactory();
        Reflector reflector = reflectorFactory.findForClass(clazz);
        List<Field> list = getAllFields(clazz);
        boolean isReadPK = false;
        boolean existTableId = isExistTableId(list);
        boolean existTableLogic = isExistTableLogic(list);
        List<TableFieldInfo> fieldList = new ArrayList(list.size());
        Iterator var12 = list.iterator();
        List<TableFieldInfo> fieldListIncludeIds = new ArrayList(list.size());
        while(var12.hasNext()) {
            Field field = (Field)var12.next();
            if (!excludeProperty.contains(field.getName())) {
                boolean isPK = false;
                boolean isOrderBy = field.getAnnotation(OrderBy.class) != null;
                if (existTableId) {
                    TableId tableId = (TableId)field.getAnnotation(TableId.class);
                    if (tableId != null) {
                        if (isReadPK) {
                            throw ExceptionUtils.mpe("@TableId can't more than one in Class: \"%s\".", new Object[]{clazz.getName()});
                        }

                        initTableIdWithAnnotation(dbConfig, tableInfo, field, tableId, reflector);
                        isReadPK = true;
                        isPK = true;
                    }
                } else if (!isReadPK) {
                    isPK = isReadPK = initTableIdWithoutAnnotation(dbConfig, tableInfo, field, reflector);
                }

                if (isPK) {
                    if (isOrderBy) {
                        tableInfo.getOrderByFields().add(new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, true));
                    }
                    TableFieldInfo fieldInfo=new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);
                    fieldListIncludeIds.add(fieldInfo);
                } else {
                    TableField tableField = (TableField)field.getAnnotation(TableField.class);
                    if (tableField != null) {
                        TableFieldInfo fieldInfo=new TableFieldInfo(dbConfig, tableInfo, field, tableField, reflector, existTableLogic, isOrderBy);
                        fieldList.add(fieldInfo);
                        fieldListIncludeIds.add(fieldInfo);
                    } else {
                        TableFieldInfo fieldInfo=new TableFieldInfo(dbConfig, tableInfo, field, reflector, existTableLogic, isOrderBy);
                        fieldList.add(fieldInfo);
                        fieldListIncludeIds.add(fieldInfo);
                    }
                }
            }
        }

        tableInfo.setFieldList(fieldList);
        if (!isReadPK) {
            logger.warn(String.format("Can not find table primary key in Class: \"%s\".", clazz.getName()));
        }
        TableUtil.setAllFields(clazz,fieldListIncludeIds);

    }

    public static boolean isExistTableId(List<Field> list) {
        return list.stream().anyMatch((field) -> {
            return field.isAnnotationPresent(TableId.class);
        });
    }

    public static boolean isExistTableLogic(List<Field> list) {
        return list.stream().anyMatch((field) -> {
            return field.isAnnotationPresent(TableLogic.class);
        });
    }

    public static boolean isExistOrderBy(List<Field> list) {
        return list.stream().anyMatch((field) -> {
            return field.isAnnotationPresent(OrderBy.class);
        });
    }

    private static void initTableIdWithAnnotation(DbConfig dbConfig, TableInfo tableInfo, Field field, TableId tableId, Reflector reflector) {
        boolean underCamel = tableInfo.isUnderCamel();
        String property = field.getName();
        if (field.getAnnotation(TableField.class) != null) {
            logger.warn(String.format("This \"%s\" is the table primary key by @TableId annotation in Class: \"%s\",So @TableField annotation will not work!", property, tableInfo.getEntityType().getName()));
        }

        if (IdType.NONE == tableId.type()) {
            tableInfo.setIdType(dbConfig.getIdType());
        } else {
            tableInfo.setIdType(tableId.type());
        }

        String column = property;
        if (StringUtils.isNotBlank(tableId.value())) {
            column = tableId.value();
        } else {
            if (underCamel) {
                column = StringUtils.camelToUnderline(property);
            }

            if (dbConfig.isCapitalMode()) {
                column = column.toUpperCase();
            }
        }

        Class<?> keyType = reflector.getGetterType(property);
        if (keyType.isPrimitive()) {
            logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", property, tableInfo.getEntityType().getName()));
        }

        tableInfo.setKeyRelated(checkRelated(underCamel, property, column)).setKeyColumn(column).setKeyProperty(property).setKeyType(keyType);
    }

    private static boolean initTableIdWithoutAnnotation(DbConfig dbConfig, TableInfo tableInfo, Field field, Reflector reflector) {
        String property = field.getName();
        if ("id".equalsIgnoreCase(property)) {
            if (field.getAnnotation(TableField.class) != null) {
                logger.warn(String.format("This \"%s\" is the table primary key by default name for `id` in Class: \"%s\",So @TableField will not work!", property, tableInfo.getEntityType().getName()));
            }

            String column = property;
            if (dbConfig.isCapitalMode()) {
                column = property.toUpperCase();
            }

            Class<?> keyType = reflector.getGetterType(property);
            if (keyType.isPrimitive()) {
                logger.warn(String.format("This primary key of \"%s\" is primitive !不建议如此请使用包装类 in Class: \"%s\"", property, tableInfo.getEntityType().getName()));
            }

            tableInfo.setKeyRelated(checkRelated(tableInfo.isUnderCamel(), property, column)).setIdType(dbConfig.getIdType()).setKeyColumn(column).setKeyProperty(property).setKeyType(keyType);
            return true;
        } else {
            return false;
        }
    }

    public static boolean checkRelated(boolean underCamel, String property, String column) {
        column = StringUtils.getTargetColumn(column);
        String propertyUpper = property.toUpperCase(Locale.ENGLISH);
        String columnUpper = column.toUpperCase(Locale.ENGLISH);
        if (!underCamel) {
            return !propertyUpper.equals(columnUpper);
        } else {
            return !propertyUpper.equals(columnUpper) && !propertyUpper.equals(columnUpper.replace("_", ""));
        }
    }

    public static List<Field> getAllFields(Class<?> clazz) {
        List<Field> fieldList = ReflectionKit.getFieldList(ClassUtils.getUserClass(clazz));
        return (List)fieldList.stream().filter((field) -> {
            TableField tableField = (TableField)field.getAnnotation(TableField.class);
            return tableField == null || tableField.exist();
        }).collect(Collectors.toList());
    }

    public static KeyGenerator genKeyGenerator(String baseStatementId, TableInfo tableInfo, MapperBuilderAssistant builderAssistant) {
        List<IKeyGenerator> keyGenerators = GlobalConfigUtils.getKeyGenerators(builderAssistant.getConfiguration());
        if (CollectionUtils.isEmpty(keyGenerators)) {
            throw new IllegalArgumentException("not configure IKeyGenerator implementation class.");
        } else {
            IKeyGenerator keyGenerator = null;
            if (keyGenerators.size() > 1) {
                KeySequence keySequence = tableInfo.getKeySequence();
                if (null != keySequence && DbType.OTHER != keySequence.dbType()) {
                    keyGenerator = (IKeyGenerator)keyGenerators.stream().filter((k) -> {
                        return k.dbType() == keySequence.dbType();
                    }).findFirst().get();
                }
            }

            if (null == keyGenerator) {
                keyGenerator = (IKeyGenerator)keyGenerators.get(0);
            }

            Configuration configuration = builderAssistant.getConfiguration();
            String id = builderAssistant.getCurrentNamespace() + "." + baseStatementId + "!selectKey";
            ResultMap resultMap = (new Builder(builderAssistant.getConfiguration(), id, tableInfo.getKeyType(), new ArrayList())).build();
            MappedStatement mappedStatement = (new MappedStatement.Builder(builderAssistant.getConfiguration(), id, new StaticSqlSource(configuration, keyGenerator.executeSql(tableInfo.getKeySequence().value())), SqlCommandType.SELECT)).keyProperty(tableInfo.getKeyProperty()).resultMaps(Collections.singletonList(resultMap)).build();
            configuration.addMappedStatement(mappedStatement);
            return new SelectKeyGenerator(mappedStatement, true);
        }
    }
}
